/*
 * Copyright 1999-2011 Alibaba Group.
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.dubbo.rpc.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

import com.alibaba.dubbo.common.HeaderProvider2;
import com.alibaba.dubbo.common.HeaderProvider2;
import com.alibaba.dubbo.common.header.HeaderProvider2Aware;
import com.alibaba.dubbo.common.resolver.annotation.ListResolver;
import com.alibaba.dubbo.common.resolver.annotation.MapResolver;
import com.alibaba.dubbo.rpc.AsynInvoker;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.Result;
import com.alibaba.dubbo.rpc.ResultListener;
import com.alibaba.dubbo.rpc.RpcContext;
import com.alibaba.dubbo.rpc.RpcInvocation;

/**
 * InvokerHandler
 * 
 * @author william.liangf
 */
public class InvokerInvocationHandler implements InvocationHandler, HeaderProvider2, HeaderProvider2Aware, AsynInvoker {

    private final Invoker<?> invoker;
    
    private Map<String, String> headers;
    public InvokerInvocationHandler(Invoker<?> handler){
        this.invoker = handler;
    }

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (method.getDeclaringClass() == Object.class) {
            return method.invoke(invoker, args);
        }
        if ("toString".equals(methodName) && parameterTypes.length == 0) {
            return invoker.toString();
        }
        if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
            return invoker.hashCode();
        }
        if ("equals".equals(methodName) && parameterTypes.length == 1) {
            return invoker.equals(args[0]);
        }
        
        if("getHeaders".equals(methodName) && parameterTypes.length == 0){
        	return this.getHeaders();
        }
        
        if(("setHeaderProvider2".equals(methodName) || "setHeaderProvider".equals(methodName)) && parameterTypes.length == 1){
        	this.setHeaderProvider2((HeaderProvider2) args[0]);
        	return null;
        }
        
        
        if("setResultListener".equals(methodName) && parameterTypes.length == 1){
        	this.setResultListener((ResultListener) args[0]);
        	return null;
        }
        if("asynCall".equals(methodName) && parameterTypes.length > 2){
        	String name = args[0] != null?args[0].toString():null;
        	//throw exception?
        	if(name == null) throw new NoSuchMethodException();
        	Method m = proxy.getClass().getMethod(name, (Class<?>[]) args[1]);
        	return invoke(proxy, m, (Object[]) args[2]);
        }
        
        List<Object> xArgs = new ArrayList<Object>();
        ResultListener listener = null;
        if(args != null){
        	for(int i = 0;i < args.length;i++){
        		if(args[i] instanceof ResultListener){
        			listener = (ResultListener)args[i];
        		}else{
        			xArgs.add(args[i]);
        		}
        	}
        }
        
        if(listener == null){
        	listener = this.listener;
        }
        
        if(xArgs.size() != args.length){
        	args = xArgs.toArray();
        }
      
        final RpcInvocation invocation = new RpcInvocation(method, args);
        
        //global headers access 
        HeaderProvider2 provider = this.getHeaderProvider2();
    	if(provider != null){
    		Map<String, String> h = provider.getHeaders();
    		if(h != null && !h.isEmpty()){
    			for(Map.Entry<String, String> entry : h.entrySet()){
            		invocation.setHeader(entry.getKey(), entry.getValue());
            	}
    		}
    	}
    	
    	//local headers access 
        if(this.headers != null && !this.headers.isEmpty()){
        	for(Map.Entry<String, String> entry : this.headers.entrySet()){
        		invocation.setHeader(entry.getKey(), entry.getValue());
        	}
        }
        RpcContext context = RpcContext.getContext();
        Map<String, String> headers = context.getRequestHeaders().getHeaders();
        if(headers != null && !headers.isEmpty()){
        	for(Map.Entry<String, String> entry : headers.entrySet()){
        		invocation.setHeader(entry.getKey(), entry.getValue());
        	}
        }
        
        //aruan 2014.09.15 , how to keep headers?
        if(listener == null){
	        context = RpcContext.getContext();
       		context.getRequestHeaders().setHeaders(invocation.getHeaders());

	        Result result = invoker.invoke(invocation);
	        context = RpcContext.getContext();
	        context.getResponseHeaders().removeHeaders();
	        Map<String, String> h = context.getResponseHeaders().getHeaders();
	        if(h != null){
	        	if(result.getHeaders() != null){
	        		h.putAll(result.getHeaders());
	        	}
	        }else{
	        	if(result.getHeaders() != null){
	        		h = new HashMap<String, String>();
	        		context.getResponseHeaders().setHeaders(h);
	        		h.putAll(result.getHeaders());
	        	}
	        }
	        return result.recreate();
        }else{
        	final ResultListener cb = listener;
        	FutureTask<Object> future = new FutureTask<Object>(new Callable<Object>(){
				@Override
				public Object call() throws Exception {
					Result result = invoker.invoke(invocation);
			        RpcContext context = RpcContext.getContext();
			        context.getResponseHeaders().removeHeaders();
			        context.getResponseHeaders().getHeaders().putAll(result.getHeaders());
			        cb.onResult(result);
					return null;
				}
        	});
        	Thread thread = new Thread(future);    
            thread.start();    
        	return future;
        }
    }

	@Override
	public Map<String, String> getHeaders() {
		if(this.headers == null){
			this.headers = new LinkedHashMap<String, String>();
		}
		return this.headers;
	}
	
	/**
	 * 设置全局参数
	 * @param headers
	 */
	public void setHeaders(Map<String, String> headers){
		
		if(headers != null && !headers.isEmpty()){
			if(this.headers == null){
				this.headers = new LinkedHashMap<String, String>();
			}
			this.headers.putAll(headers);
		}
	}

	private HeaderProvider2 headersProvider;
	@Override
	public void setHeaderProvider2(HeaderProvider2 provider) {
		this.headersProvider = provider;
	}

	@Override
	public HeaderProvider2 getHeaderProvider2() {
		return this.headersProvider;
	}

	@Override
	public String getHeader(String key) {
		if(this.headers == null) return null;
		return this.headers.get(key);
	}

	@Override
	public String getHeader(String key, String defaultValue) {
		if(this.headers == null) return null;
		String value = this.headers.get(key);
		return (value == null || value.length() == 0) ?defaultValue:value;
	}

	@Override
	public String removeHeader(String key) {
		if(this.headers != null){
			return this.headers.remove(key);
		}
		return null;
	}
	
	@Override
	public void removeHeaders() {
		if(this.headers != null){
			this.headers.clear();
		}
	}

	@Override
	public <T> Future<T> asynCall(String method, Class<?>[] parameterTypes, Object... args) {
		return null;
	}

	ResultListener listener;
	@Override
	public void setResultListener(ResultListener listener) {
		this.listener = listener;
	}

	@Override
	public void setHeader(String key, String value) {
		if(this.headers  == null){
			this.headers = new HashMap<String, String>();
		}
		this.headers.put(key, value);
	}

	@Override
	public boolean containsHeader(String key) {
		if(this.headers == null) return false;
		return this.headers.containsKey(key);
	}
}